package com.unibuy.smartdevice.devices;

import com.unibuy.smartdevice.exception.Logs;
import com.unibuy.smartdevice.exception.LogsEmptyException;

public class DevXinYuan {
    public enum CMD {
        CMD_FAFB_STX(new byte[]{(byte) 0xFA, (byte) 0xFB}),
        CMD_42_ACK(new byte[]{0x42, 0x00}),
        CMD_25_GIVE_CHANGE(new byte[]{0x25, 0x05}), //啟動找零
        CMD_27_IN_COME(new byte[]{0x27, 0x06}), //1:紙幣 2:硬幣 8:吞幣
        CMD_28_SET_MONEY_TYPE(new byte[]{0x28, 0x04}), //設定接收面值 類型 0:紙幣 1硬幣
        CMD_12_SET_PRODUCT_PRICE(new byte[]{0x12, 0x07}),
        CMD_13_SET_PRODUCT_STOCK(new byte[]{0x13, 0x04}),
        CMD_14_SET_SLOT_SIZE(new byte[]{0x14, 0x04}),
        CMD_15_SET_SLOT_INDEX(new byte[]{0x15, 0x05}),
        CMD_01_CHECK_SLOT_STATUS(new byte[]{0x01, 0x03}),
        CMD_03_BUY_PRODUCT(new byte[]{0x03, 0x03}), //出貨1 4.3.2
        CMD_05_SELECT_UNSELECT_SLOT(new byte[]{0x05, 0x03}),
        CMD_06_GET_PRODUCT(new byte[]{0x06, 0x06}), //出貨2 4.3.5
        CMD_53_CHECK_MACHINE_STATUS(new byte[]{0x53, 0x01}),
        CMD_16_SET_POLL_MS(new byte[]{0x16, 0x02}), //1:100ms~20:2000ms
        CMD_31_SYNC_PACK_NO(new byte[]{0x31, 0x01}),
        CMD_36_GET_VERSION(new byte[]{0x36, 0x01}),
        CMD_37_GIVE_CHANGE_MODE(new byte[]{0x37, 0x02}),
        CMD_51_CHECK_VMC_STATUS(new byte[]{0x51, 0x01}),
        CMD_70_MENU_SET(new byte[]{0x70, 0x01}),
        ;

        private byte[] bytes;
        CMD(byte[] bytes) {
            this.bytes = bytes;
        }

        public byte[] getBytes() {
            return bytes;
        }
    }

    public enum VMC {
        VMC_FAFB_STX(new byte[]{(byte) 0xFA, (byte) 0xFB}),
        VMC_41_POLL(new byte[]{0x41, 0x00}),
        VMC_42_ACK(new byte[]{0x42, 0x00}),
        VMC_84_ACK(new byte[]{(byte) 0x84, 0x01}),
        VMC_21_GET_MONEY(new byte[]{0x21, 0x06}), //收錢通知:PackON+方式1紙幣2硬幣+金額(4byte)
        VMC_23_NOW_GET_TOTAL(new byte[]{0x23, 0x05}), //當前金額
        VMC_24_POS_DISPLAY(new byte[]{0x24, 0x19}),
        VMC_26_GIVE_CHANGE(new byte[]{0x26, 0x09}),
        VMC_27_IN_COME(new byte[]{0x27, 0x06}), //1:紙幣 2:硬幣 8:吞幣
        VMC_29_MONEY_SWITCH(new byte[]{0x29, 0x02}),
        VMC_11_GIVE_PRODUCT_INFO(new byte[]{0x11, 0x12}),
        VMC_12_SET_PRODUCT_PRICE(new byte[]{0x12, 0x07}),
        VMC_13_SET_PRODUCT_STOCK(new byte[]{0x13, 0x04}),
        VMC_14_SET_SLOT_SIZE(new byte[]{0x14, 0x04}),
        VMC_15_SET_SLOT_INDEX(new byte[]{0x15, 0x05}),
        VMC_17_SET_STOCK_MAX(new byte[]{0x17, 0x01}),
        VMC_02_CHECK_SLOT_STATUS(new byte[]{0x02, 0x04}),
        VMC_04_BUY_STATUS(new byte[]{0x04, 0x05}), //出貨3
        VMC_05_SELECT_UNSELECT_SLOT(new byte[]{0x05, 0x03}), //選擇或不選擇貨道
        VMC_54_CHECK_MACHINE_STATUS(new byte[]{0x54, 0x01}),
        VMC_31_SYNC_PACK_NO(new byte[]{0x31, 0x01}),
        VMC_32_GET_VERSION(new byte[]{0x32, 0x01}),
        VMC_33_PASS_GIVE_CHANGE(new byte[]{0x33, 0x00}),
        VMC_38_GIVE_CHANGE_MODE(new byte[]{0x38, 0x02}),
        VMC_52_CHECK_VMC_STATUS(new byte[]{0x52, 0x01}),
        VMC_63_CLICK_RED_BUTTON(new byte[]{0x63, 0x01}),
        VMC_66_PO_MICROWAVE_STATUS(new byte[]{0x66, 0x01}),
        VMC_67_PO_MACHINE_STATUS(new byte[]{0x67, 0x01}),
        VMC_71_MENU_GET(new byte[]{0x71, 0x01}),
        ;

        private byte[] bytes;
        VMC(byte[] bytes) {
            this.bytes = bytes;
        }

        public byte[] getBytes() {
            return bytes;
        }
    }

    public enum GIVE_MONEY_26 {
        GET_MONEY_01_BILL(new byte[]{0x01}),
        GET_MONEY_02_COIN(new byte[]{0x02}),
        GET_MONEY_08_GET_COIN(new byte[]{0x08}),
        ;

        private byte[] bytes;
        GIVE_MONEY_26(byte[] bytes) {
            this.bytes = bytes;
        }

        public byte[] getBytes() {
            return bytes;
        }
    }

    public enum SLOT_STATUS_02 {
        SLOT_STATUS_01_OK(new byte[]{0x01}),
        SLOT_STATUS_02_PDT_EMPTY(new byte[]{0x02}),
        SLOT_STATUS_03_SLOT_EMPTY(new byte[]{0x03}),
        SLOT_STATUS_04_SLOT_TMP_USE(new byte[]{0x04}),
        SLOT_STATUS_05(new byte[]{0x05}),
        SLOT_STATUS_06_NOT_CLOSE(new byte[]{0x06}),
        SLOT_STATUS_07_ERROR(new byte[]{0x07}),
        SLOT_STATUS_08_ERROR(new byte[]{0x08}),
        SLOT_STATUS_09_ERROR(new byte[]{0x09}),
        SLOT_STATUS_0A_ERROR(new byte[]{0x0A}),
        SLOT_STATUS_10_ERROR(new byte[]{0x10}),
        SLOT_STATUS_11_ERROR(new byte[]{0x11}),
        SLOT_STATUS_12_BOX_EMPTY(new byte[]{0x12}),
        SLOT_STATUS_13_BOX_HOT(new byte[]{0x13}),
        SLOT_STATUS_14_ERROR(new byte[]{0x14}),
        SLOT_STATUS_15_PLIS_BOX(new byte[]{0x15}),
        SLOT_STATUS_16_ERROR(new byte[]{0x16}),
        SLOT_STATUS_17_ERROR(new byte[]{0x17}),
        SLOT_STATUS_18_ERROR(new byte[]{0x18}),
        SLOT_STATUS_19_ERROR(new byte[]{0x19}),
        SLOT_STATUS_20_ERROR(new byte[]{0x20}),
        SLOT_STATUS_21_ERROR(new byte[]{0x21}),
        SLOT_STATUS_22_ERROR(new byte[]{0x22}),
        SLOT_STATUS_23_ERROR(new byte[]{0x23}),
        SLOT_STATUS_24(new byte[]{0x24}),
        SLOT_STATUS_25(new byte[]{0x25}),
        SLOT_STATUS_26(new byte[]{0x26}),
        SLOT_STATUS_27(new byte[]{0x27}),
        SLOT_STATUS_28(new byte[]{0x28}),
        SLOT_STATUS_29(new byte[]{0x29}),
        SLOT_STATUS_30(new byte[]{0x30}),
        SLOT_STATUS_31(new byte[]{0x31}),
        SLOT_STATUS_32(new byte[]{0x32}),
        SLOT_STATUS_FF(new byte[]{(byte) 0xFF});

        private byte[] bytes;
        SLOT_STATUS_02(byte[] bytes) {
            this.bytes = bytes;
        }

        public byte[] getBytes() {
            return bytes;
        }
    }

    public enum BUY_STATUS_04 {
        BUY_STATUS_01_NOW(new byte[]{0x01}),
        BUY_STATUS_02_OK(new byte[]{0x02}),
        BUY_STATUS_03_K_PDT(new byte[]{0x03}),
        BUY_STATUS_04_NOT_STOP(new byte[]{0x04}),
        BUY_STATUS_05(new byte[]{0x05}),
        BUY_STATUS_06_NOT_FOUND(new byte[]{0x06}),
        BUY_STATUS_07_ERROR(new byte[]{0x07}),
        BUY_STATUS_08_ERROR(new byte[]{0x08}),
        BUY_STATUS_09_ERROR(new byte[]{0x09}),
        BUY_STATUS_0A_ERROR(new byte[]{0x0A}),
        BUY_STATUS_10_PLATFORM_UP(new byte[]{0x10}),
        BUY_STATUS_11_PLATFORM_DOWN(new byte[]{0x11}),
        BUY_STATUS_12_PLATFORM_UP_ERROR(new byte[]{0x12}),
        BUY_STATUS_13_PLATFORM_DOWN_ERROR(new byte[]{0x13}),
        BUY_STATUS_14_GET_DOOR_CLOSE(new byte[]{0x14}),
        BUY_STATUS_15_GET_DOOR_ERROR(new byte[]{0x15}),
        BUY_STATUS_16_SEND_DOOR_OPEN(new byte[]{0x16}),
        BUY_STATUS_17_SEND_DOOR_OPEN_ERROR(new byte[]{0x17}),
        BUY_STATUS_18_BOX_SEND_NOW(new byte[]{0x18}),
        BUY_STATUS_19_SEND_DOOR_CLOSE(new byte[]{0x19}),
        BUY_STATUS_20_SEND_DOOR_CLOSE_ERROR(new byte[]{0x20}),
        BUY_STATUS_21_BOX_EMPTY(new byte[]{0x21}),
        BUY_STATUS_22_BOX_HOT(new byte[]{0x22}),
        BUY_STATUS_23_BOX_HOT_TIME(new byte[]{0x23}),
        BUY_STATUS_24_PLIS_BOX(new byte[]{0x24}),
        BUY_STATUS_25_ERROR(new byte[]{0x25}),
        BUY_STATUS_26_MICROWAVE_DOOR_OPEN(new byte[]{0x26}),
        BUY_STATUS_27_MICROWAVE_DOOR_OPEN_ERROR(new byte[]{0x27}),
        BUY_STATUS_28_ERROR(new byte[]{0x28}),
        BUY_STATUS_29_ERROR(new byte[]{0x29}),
        BUY_STATUS_30_ERROR(new byte[]{0x30}),
        BUY_STATUS_31_ERROR(new byte[]{0x31}),
        BUY_STATUS_32_ERROR(new byte[]{0x32}),
        BUY_STATUS_FF_SHAPING_STOP(new byte[]{(byte) 0xFF});

        private byte[] bytes;
        BUY_STATUS_04(byte[] bytes) {
            this.bytes = bytes;
        }

        public byte[] getBytes() {
            return bytes;
        }
    }

    public enum MACHINE_STATUS_54 {
        MACHINE_STATUS_00_OK(new byte[(byte)0x00]),
        MACHINE_STATUS_01_UPDOWN_BOX_HF(new byte[0x01]),
        MACHINE_STATUS_02_GET_DOOR_NOT_CLOSE(new byte[0x02]),
        MACHINE_STATUS_03_UPDOWN_ERROR(new byte[0x03]),
        MACHINE_STATUS_04_UPDOWN_ERROR2(new byte[0x04]);

        private byte[] bytes;
        MACHINE_STATUS_54(byte[] bytes) {
            this.bytes = bytes;
        }

        public byte[] getBytes() {
            return bytes;
        }
    }

    private Logs logs;
    private boolean isRun;
    private int retryCount = 0;
    private byte packNo = 0x00;

    public boolean isRun() {
        return isRun;
    }

    public void setRun(boolean run) {
        isRun = run;
    }

    public int getRetryCount() {
        return retryCount;
    }

    public int addRetryCount() {
        return this.retryCount+=1;
    }

    public int initRetryCount() {
        return this.retryCount = 0;
    }

    public boolean isRetryMax() {
        logs.info("retryCount:"+this.retryCount);
        boolean isRetryMax = this.retryCount >= 5;
        return isRetryMax;
    }

    public boolean isRetryMin() {
        boolean isRetryMin = this.retryCount >= 1;
        return isRetryMin;
    }

    public DevXinYuan() {
        logs = new Logs(this.getClass());
        this.packNo = 0x00;
    }

    public byte getXOR(byte[] buffer) {
        byte checksum = 0x00;

        for (byte value : buffer) {
            checksum ^= value; // 按位異或
        }

        return checksum;
    }

    public byte[] send(CMD cmd, byte[] dataBytes) {
        return send(cmd, dataBytes, true);
    }

    public byte[] send(CMD cmd, byte[] dataBytes, boolean isShow) {
        byte[] proBytes = CMD.CMD_FAFB_STX.getBytes();
        proBytes = PortTools.concatByteArrays(proBytes, cmd.getBytes());
        proBytes = PortTools.concatByteArrays(proBytes, dataBytes);
        byte[] XORByte = new byte[] {getXOR(proBytes)};
        proBytes = PortTools.concatByteArrays(proBytes, XORByte);

        if (isShow) {
            logs.info("XinYuan ipcCmd:" + cmd.name() + " " + PortTools.showHex(proBytes));
        }

        return proBytes;
    }

    public byte[] syncPackNo(byte packNo) {
        this.packNo = packNo;
        return send(CMD.CMD_31_SYNC_PACK_NO, new byte[] {
                packNo
        });
    }

    public void setPackNo(byte packNo) {
        this.packNo = packNo;
    }

    public byte getPackNo(byte addIndex) {
        this.packNo += addIndex;
//        logs.info("Send packNo:" + PortTools.showHex(packNo));
        return this.packNo;
    }

    public byte[] sendACK() {
        return send(CMD.CMD_42_ACK, new byte[0]);
    }

    public byte[] sendACKByIsShow(boolean isShow) {
        return send(CMD.CMD_42_ACK, new byte[0], isShow);
    }

    //1:紙幣 2:硬幣 8:紙幣和硬幣
    //0:0000 1:0001 2:0010 4:0100 8:1000
    public byte[] inComeByIPC(byte mode, int money) {
        byte[] bytes = PortTools.intToByte8(money, 4);
        return send(CMD.CMD_27_IN_COME, new byte[]{
                getPackNo((byte)0x01), mode, bytes[0], bytes[1], bytes[2], bytes[3]
        });
    }

    //設定現金機可吃鈔或吃幣
    public byte[] setMoneyType(byte mode, byte set1, byte set2) {
        return send(CMD.CMD_28_SET_MONEY_TYPE, new byte[]{
                getPackNo((byte)0x01), mode, set1, set2
        });
    }
    //1:紙幣 2:硬幣
    //0:不接收 65535:全接收
    public byte[] setMoneyType(byte mode, int parValue) {
        byte[] bytes = PortTools.intToByte8(parValue, 2);
        return send(CMD.CMD_28_SET_MONEY_TYPE, new byte[]{
                getPackNo((byte)0x01), mode, bytes[0], bytes[1]
        });
    }

    public byte[] setPollMs(byte ms) {
        return send(CMD.CMD_16_SET_POLL_MS, new byte[] {
                getPackNo((byte)0x01), ms
        });
    }

    public byte[] buyProduct(int slot) {
        byte[] buffer = PortTools.intToByte8(slot, 2);
        return send(CMD.CMD_03_BUY_PRODUCT, new byte[]{
                getPackNo((byte)0x01), buffer[0], buffer[1]
        });
    }

    public byte[] getProduct(int slot) {
        byte[] buffer = PortTools.intToByte8(slot, 2);
        return send(CMD.CMD_06_GET_PRODUCT, new byte[]{
                getPackNo((byte)0x01), 0x01, 0x01, buffer[0], buffer[1], 0x00
        });
    }

    public byte[] checkSlot(int slot) {
        byte[] buffer = PortTools.intToByte8(slot, 2);
        return send(CMD.CMD_01_CHECK_SLOT_STATUS, new byte[] {
                getPackNo((byte)0x01), buffer[0], buffer[1]
        });
    }

    public byte[] giveChangeMode(boolean isIPC) {
        return send(CMD.CMD_37_GIVE_CHANGE_MODE, new byte[] {
                getPackNo((byte)0x01), isIPC ? (byte)0x01: (byte)0x02
        });
    }

    //GIVE_CHANGE
    public byte[] giveChange(int price) {
        byte[] buffer = PortTools.intToByte8(price*100, 4);
        return send(CMD.CMD_25_GIVE_CHANGE, new byte[] {
                getPackNo((byte)0x01), buffer[0], buffer[1], buffer[2], buffer[3]
        });
    }

    public CMD readCMD(byte[] buffer) throws LogsEmptyException {
        logs.info(PortTools.showHex(buffer));

        for (CMD cmd : CMD.values()) {
            if (cmd.getBytes().length >= 2 && buffer.length >= 2) {
                if (cmd.getBytes()[0] == buffer[0]) {
                    if (cmd.equals(CMD.CMD_FAFB_STX)) {
                        if (cmd.getBytes()[1] == buffer[1]) {
                            logs.info("CMD:" + cmd.name() + " " + PortTools.showHex(cmd.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]}));
                            return cmd;
                        }
                    } else {
                        logs.info("CMD:" + cmd.name() + " " + PortTools.showHex(cmd.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]}));
                        return cmd;
                    }
                }
            }
        }

        throw new LogsEmptyException(logs, "cmd command not found:"+PortTools.showHex(buffer));
    }

    public VMC readVMC(byte[] buffer) throws LogsEmptyException {
        logs.info(PortTools.showHex(buffer));

        for (VMC vmc : VMC.values()) {
            if (vmc.getBytes().length >= 2 && buffer.length >= 2) {
                if (vmc.getBytes()[0] == buffer[0]) {
                    if (vmc.equals(VMC.VMC_FAFB_STX)) {
                        if (vmc.getBytes()[1] == buffer[1]) {
                            logs.info("VMC:" + vmc.name() + " " + PortTools.showHex(vmc.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]}));
                            return vmc;
                        }
                    } else {
                        logs.info("VMC:" + vmc.name() + " " + PortTools.showHex(vmc.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]}));
                        return vmc;
                    }
                }
            }
        }

        throw new LogsEmptyException(logs, "vmc command not found:"+PortTools.showHex(buffer));
    }

    public byte readPackNo(byte[] buffer) {
        byte packNo = buffer[0];
        logs.info("Read packNo:" + PortTools.showHex(packNo));
        return packNo;
    }

    public BUY_STATUS_04 readBuyStatus(byte[] buffer) throws LogsEmptyException {
        for (BUY_STATUS_04 buyStatus : BUY_STATUS_04.values()) {
            if (buyStatus.getBytes().length == 1 && buffer.length > 0) {
                if (buyStatus.getBytes()[0] == buffer[1]) {
                    logs.info(BUY_STATUS_04.class.getSimpleName()+":" + buyStatus.name() + " " + PortTools.showHex(buyStatus.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]}));
                    return buyStatus;
                }
            }
        }

        throw new LogsEmptyException(logs, "buy status command not found");
    }

    public SLOT_STATUS_02 readSlotStatus(byte[] buffer) throws LogsEmptyException {
        for (SLOT_STATUS_02 slotStatus : SLOT_STATUS_02.values()) {
            if (slotStatus.getBytes().length == 1 && buffer.length > 0) {
                if (slotStatus.getBytes()[0] == buffer[1]) {
                    logs.info(SLOT_STATUS_02.class.getSimpleName()+":" + slotStatus.name() + " " + PortTools.showHex(slotStatus.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]}));
                    return slotStatus;
                }
            }
        }

        throw new LogsEmptyException(logs, "buy status command not found");
    }

    //GIVE_MONEY
    public GIVE_MONEY_26 readGiveMoney(byte[] buffer) throws LogsEmptyException {
        for (GIVE_MONEY_26 giveMoney : GIVE_MONEY_26.values()) {
            if (giveMoney.getBytes().length == 1 && buffer.length > 0) {
                if (giveMoney.getBytes()[0] == buffer[1]) {
                    logs.info(GIVE_MONEY_26.class.getSimpleName()+":" + giveMoney.name() + " " + PortTools.showHex(giveMoney.getBytes()) + " buffer:" + PortTools.showHex(new byte[]{buffer[0], buffer[1]}));
                    return giveMoney;
                }
            }
        }

        throw new LogsEmptyException(logs, "buy status command not found");
    }

    public int readMoney(byte[] buffer, int strPos, int endPos) {
        byte[] result = new byte[endPos - strPos +1];
        for (int i = strPos; i <= endPos; i++) {
            result[i-strPos] = buffer[i];
        }
        return PortTools.byte8ToInt(result);
    }

    public byte[] readData(byte[] buffer) throws LogsEmptyException {
        int length = buffer[1];
        byte[] proBytes = VMC.VMC_FAFB_STX.getBytes();
        byte[] cmdLenData = PortTools.catByteArrays(buffer, 0, buffer.length-1);
        byte XORByte = buffer[buffer.length -1];
        proBytes = PortTools.concatByteArrays(proBytes, cmdLenData);
        byte XORByte2 = getXOR(proBytes);
        logs.info("Length:"+length+" XOR1:" + PortTools.showHex(new byte[] {XORByte}) + " XOR2:" + PortTools.showHex(new byte[] {XORByte2}));
        if (buffer.length > (2+length)) {
            byte[] dataBytes = PortTools.catByteArrays(buffer, 2, length);
            logs.info("data:"+PortTools.showHex(dataBytes));
            return dataBytes;
        } else {
            throw new LogsEmptyException(logs, "data array length insufficient");
        }
    }
}
